package com.br4mmie.asciistory.mission;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;

import com.br4mmie.asciistory.*;
import com.br4mmie.asciistory.battle.*;
import com.br4mmie.asciistory.characters.*;
import com.br4mmie.asciistory.shop.*;
import com.br4mmie.asciistory.ui.*;


/**
 * The mission class manages a mission and it's objectives
 * @author Bram Buurlage
 */
public class Mission implements Restorable
{
	private ArrayList<Objective> objectives = new ArrayList<Objective>();
	private int 	status;
	private boolean started;
	private boolean added;
	private String 	name;
	private String  filename;
	private String	animationFilename;
	private ShopWindow shopWindow;
	
	//update info
	private ArrayList<Mission> journal;
	private ArrayList<String>  monstersKilled = new ArrayList<String>();
	private Player 			   player;
	
	//pending results
	private ArrayList<String>  paragraphs = new ArrayList<String>();
	private String			   newMission;

	//objectives
	public abstract class Objective 
	{
		public boolean finished = false;
		
		/**
		 * handle the Objective
		 * @param owner the mission that owns this
		 * @return whether the handler should move on to the next objective after handling this one
		 */
		public abstract boolean handle(Mission owner);
		/**
		 * same as handle, but thisone is called when the Objective is finished already
		 * @param owner the mission that owns this
		 */
		public abstract void    review(Mission owner);
	}
	public abstract class PlayerObjective extends Objective {}
	public abstract class NPCObjective extends Objective {}
	public class FinishedMission extends NPCObjective
	{
		public String missionname;
		public String toString()
		{
			return "Complete "+missionname+" first.";
		}

		public boolean handle(Mission owner) 
		{
			for (Mission m: owner.journal)
			{
				if (m.getName().equals(missionname) && m.isFinished())
				{
					finished = true;
					return true;
				}
			}
			return false;
		}
		public void review(Mission owner) 
		{
		}
	}
	public class Talk 		extends NPCObjective
	{
		String paragraph;
		public String toString()
		{
			return "Talk to your questmaster";
		}

		public boolean handle(Mission owner) 
		{
			if (!owner.previousFinished()) return false;
			
			finished = true;
			owner.started = true;
			paragraphs.add(paragraph);
			
			return owner.nextIsSame(this);
		}
		public void review(Mission owner) 
		{
			//if (owner.isLastOfType(this))
			//{
			//	paragraphs.add(paragraph);
			//}
		}
	}
	public class Collect 	extends PlayerObjective
	{
		public String kind;
		public int count;
		public String toString()
		{
			if (count == 1) return "Collect 1 "+kind;
			return "Collect "+count+" "+kind+"s";
		}

		public boolean handle(Mission owner) 
		{
			owner.added = true;
			int needed = count;
			
			for (InventoryItem i: owner.player.getInventory())
			{
				if (i.item.getName().equals(kind)) needed -= i.amount;
			}
			
			if (needed <= 0) finished = true;
			return true;
		}
		public void review(Mission owner) 
		{
		}
	}
	public class Hunt 		extends PlayerObjective
	{
		public String kind;
		public int count;
		public int left;
		public String toString()
		{
			return "Kill "+count+" "+kind+"'s\n("+left+" left)";
		}
		
		@SuppressWarnings("unchecked")
		public boolean handle(Mission owner) 
		{
			owner.added = true;
			ArrayList<String> mbackup = (ArrayList<String>)monstersKilled.clone();
			for (String monster: mbackup)
			{
				if (monster.equals(kind))
				{
					monstersKilled.remove(monster);
					left--;
					
					if (left <= 0) finished = true;
				}
			}
			return true;
		}
		public void review(Mission owner) 
		{
		}
	}
	public class Reward 	extends NPCObjective
	{
		public String kind;
		public int count;
		public String toString()
		{
			return "";
		}

		public boolean handle(Mission owner)
		{
			finished = true;
			if (kind.equals("Gold"))
			{
				owner.player.getInventory().receiveGold(count);
			}
			else if (kind.equals("Exp"))
			{
				owner.player.gainExp(count);
			}
			else
			{
				for (int i=0; i<count; i++)
				{
					try 
					{
						owner.player.getInventory().receiveItem(Item.fromFile(kind));
					} catch (NumberFormatException e) 
					{
						e.printStackTrace();
					} catch (IOException e) 
					{
						e.printStackTrace();
					}
				}
			}
			return true;
		}
		public void review(Mission owner) 
		{
		}
	}
	public class RemoveItem extends NPCObjective
	{
		public String kind;
		public int count;
		public String toString()
		{
			return "";
		}

		public boolean handle(Mission owner) 
		{
			finished = true;
			for (int i=0; i<count; i++)
			{
				InventoryItem ii = owner.player.getInventory().findItem(kind);
				owner.player.getInventory().removeItem(ii);
			}
			return true;
		}
		public void review(Mission owner) 
		{
		}
	}
	public class SwapMission extends NPCObjective
	{
		public String newMission;

		public boolean handle(Mission owner) 
		{
			finished = true;
			owner.newMission = this.newMission;
			return true;
		}
		public void review(Mission owner) 
		{
			owner.newMission = this.newMission;
		}
	}
	public class ShowShop 	extends NPCObjective
	{
		public Inventory seller;

		public boolean handle(Mission owner) 
		{
			finished = true;	
			return true;
		}
		public void review(Mission owner) 
		{
			if (owner.isLastOfType(this))
			{
				Shop shop = new Shop(seller, player.getInventory());
				if (shopWindow != null) shopWindow.close();
				shopWindow = new ShopWindow(shop);
			}
		}
	}
	public class EndBoss	extends PlayerObjective
	{
		EnemyType[] endboss;

		public boolean handle(Mission owner) 
		{
			CharImage background = new CharImage(ASCIIStoryActivity.getScreenWidth()-1, ASCIIStoryActivity.getScreenHeight()-1);
			background.fill(' ');

			new BattleWindow(endboss, background, ASCIIStoryActivity.getGameLoop().randomEncounter);
			
			finished = true;
			return true;
		}
		public void review(Mission owner) 
		{
		}
	}
	
	/**
	 * restore a mission from a savefile
	 * @param reader the reader of the savefile
	 * @param filename the filename to load a mission from
	 * @param player the player
	 * @return the restored mission object. it will be added to the player if it was before
	 */
	public static Mission restore(BufferedReader reader, String filename, Player player)
	{
		Mission mission = null;
		try 
		{
			mission = new Mission(filename);
			if (reader.readLine().endsWith("1")) mission.started = true;
			if (reader.readLine().endsWith("1")) mission.added   = true;
			if (mission.added) player.restoreMission(mission);
			for (Objective o: mission.objectives)
			{
				String line = reader.readLine();
				if (line != null)
				{
					if (line.endsWith("end")) break;
					if (o instanceof Hunt)
					{
						Hunt h = (Hunt)o;
						h.left = Integer.parseInt(line.split(" ")[1]);
						if (h.left <= 0) h.finished = true;
					}
					else
					{
						o.finished = line.endsWith("1");
					}
				}
			}
		} catch (Exception e) 
		{
			e.printStackTrace();
		}
		return mission;
	}
	public Mission(String filename) throws Exception
	{
		this.filename = filename;
		status = 0;
		started = false;
		name = "";
		
		BufferedReader file = new BufferedReader(new FileReader(FileGetter.getFilename(filename)));
		while (true)
		{
			String line = file.readLine();
			if (line == null) break;
			
			String content = line.substring(2);
			if		  (line.startsWith("f "))
			{
				FinishedMission mission = new FinishedMission();
				mission.missionname = content;
				
				objectives.add(mission);
			} else if (line.startsWith("n "))
			{
				name = content;
			} else if (line.startsWith("t "))
			{
				Talk talk = new Talk();
				talk.paragraph = content;
				
				objectives.add(talk);
			} else if (line.startsWith("c "))
			{
				Collect collect = new Collect();
				collect.kind  = content.substring(0, content.indexOf(' '));
				collect.count = Integer.parseInt(content.substring(content.indexOf(' ')+1));
				
				objectives.add(collect);
			} else if (line.startsWith("h "))
			{
				Hunt hunt = new Hunt();
				hunt.kind  = content.substring(0, content.indexOf(' '));
				hunt.count = Integer.parseInt(content.substring(content.indexOf(' ')+1));
				hunt.left = hunt.count;
				
				objectives.add(hunt);
			} else if (line.startsWith("r "))
			{
				Reward reward = new Reward();
				reward.kind  = content.substring(0, content.indexOf(' '));
				reward.count = Integer.parseInt(content.substring(content.indexOf(' ')+1));
				
				objectives.add(reward);
			} else if (line.startsWith("an "))
			{
				animationFilename = line.substring(3);
			} else if (line.startsWith("a "))
			{
				RemoveItem remove = new RemoveItem();
				remove.kind  = content.substring(0, content.indexOf(' '));
				remove.count = Integer.parseInt(content.substring(content.indexOf(' ')+1));
				
				objectives.add(remove);
			} else if (line.startsWith("m "))
			{
				SwapMission mission = new SwapMission();
				mission.newMission = content;
				
				objectives.add(mission);
			} else if (line.startsWith("s "))
			{
				ShowShop showshop = new ShowShop();
				
				String[] shopItems = line.substring(2).split(" ");
				Inventory seller = new Inventory(20);
				for (String itemFile: shopItems)
				{
					Item item = Item.fromFile(itemFile);
					InventoryItem invItem = new InventoryItem(item, 9, 9);
					seller.add(invItem);
				}
				showshop.seller = seller;
				
				objectives.add(showshop);
			} else if (line.startsWith("e "))
			{
				EndBoss endboss = new EndBoss();
				endboss.endboss = new EnemyType[] {new EnemyType(content)};
				
				objectives.add(endboss);
			}
		}
	}
	
	/**
	 * return the animation file
	 * @return returns the animation file, can be null
	 */
	public String			getAnimationFilename()
	{
		return animationFilename;
	}
	
	/**
	 * go to the begin of the last talk
	 */
	public void 			goToBeginOfText()
	{
		status--;
		while (status >= 0 && objectives.get(status) instanceof Talk) status--;
		status++;
	}
	/**
	 * go the first unfinished objective
	 */
	public void 			goToFirstUnfininished()
	{
		status = 0;
		while (status < objectives.size() && objectives.get(status).finished)
		{
			status++;
		}
	}
	/**
	 * check if the previous objective is finished
	 * @return whether the previous objective is finished
	 */
	public boolean			previousFinished()
	{
		if (status == 0) return true;
		return objectives.get(status-1).finished;
	}
	/**
	 * check if o is the last of it's type that is finished
	 * @param o the objective to check
	 * @return whether o is the last of it's type
	 */
	public boolean			isLastOfType(Objective o)
	{
		for (int i=objectives.size()-1; i>0; i--)
		{
			if (objectives.get(i).equals(o)) 							return true;
			if (	objectives.get(i).getClass().equals(o.getClass()) 
				&& 	objectives.get(i).finished) 						return false;
		}
		return false;
	}
	/**
	 * check if the next objective is the same type as o
	 * @param o the objective to check
	 * @return whether the next objective is the same
	 */
	public boolean 			nextIsSame(Objective o)
	{
		if (status < objectives.size()-1)
		{
			if (objectives.get(status+1).getClass().equals(o.getClass())) return true;
		}
		return false;
	}
	/**
	 * handles the objectives
	 * @param journal an arraylist with missions
	 * @param player the player
	 * @param handleTalks whether talks and shops should be handled 
	 * (this should generally only happen when you talk to a NPC)
	 */
	public void				handleObjectives(ArrayList<Mission> journal, Player player, boolean handleTalks)
	{
		shopWindow = null;
		this.journal 		= journal;
		this.player 		= player;
		status = 0;
		while (true)
		{
			Objective o = objectives.get(status);
			if (!o.finished)
			{
				if (!handleTalks)
				{
					if (!(o instanceof Talk))
					{
						if (!o.handle(this)) break;
					}
					else
					{
						break;
					}
				}
				else
				{
					if (!o.handle(this)) break;
				}
			}
			else
			{
				if (!(objectives.get(status) instanceof ShowShop))
				{
					objectives.get(status).review(this);
				}
				else if (handleTalks) 
				{
					objectives.get(status).review(this);
				}
			}
			
			if (++status >= objectives.size()) break;
		}
	}
	
	/**
	 * get next paragraph for talk
	 * @return returns paragraph as a string
	 */
	public String 			getParagraph()
	{
		if (paragraphs.size() > 0)
		{
			return paragraphs.remove(0);
		}
		else
		{
			return null;
		}
	}
	/**
	 * set whether this mission has been added to the players mission list
	 * @param added obvious
	 */
	public void 			setAdded(boolean added)
	{
		this.added = added;
	}
	/**
	 * get this mission if it is allowed
	 * @return returns this if allowed, otherwise null
	 */
	public Mission 			getMission()
	{
		if (started && !added) return this;
		return null;
	}
	/**
	 * return this if the mission is started
	 * @return this, if started
	 */
	public Mission 			getStartedMission()
	{
		if (started) return this;
		return null;
	}
	/**
	 * return the next mission if there is any pending
	 * otherwise returns null
	 * @return filename to the next mission, or null
	 */
	public String 			getNewMission()
	{
		String tmp = newMission;
		newMission = null;
		return tmp;
	}
	/**
	 * update any pending Hunt objectives, after a monster got killed
	 * @param name the name of the monster that got killed
	 */
	public void 			killMonster(String name, Player player)
	{
		monstersKilled.add(name);
	}
	/**
	 * shows an info window with objectives
	 */
	public void				showInfoWindow()
	{
		new MissionWindow(this).activate();
	}
	/**
	 * @return returns whether the mission is finished or not
	 */
	public boolean 			isFinished()
	{
		boolean f = true;
		for (Objective o: objectives)
		{
			f &= o.finished;
		}
		
		return f;
	}
	/**
	 * @return returns the missions name
	 */
	public String 			getName()
	{
		return name;
	}
	/**
	 * @return returns pending objectives
	 */
	public List<Objective>  getObjectives()
	{
		ArrayList<Objective> obj = new ArrayList<Objective>();
		goToFirstUnfininished();
		
		
		if (status < objectives.size())
		{
			obj.add(objectives.get(status++));
			while (status < objectives.size() && (objectives.get(status) instanceof Collect ||
													objectives.get(status) instanceof Hunt))
			{
				obj.add(objectives.get(status++));
			}
		}
			
		return obj;
	}

	/**
	 * save to a savefile
	 */
	public void save(BufferedWriter writer) 
	{
		try 
		{
			writer.write("mission "+filename+"\n");
			writer.write("started "+(started?"1\n":"0\n"));
			writer.write("added "+(added?"1\n":"0\n"));
			for (Objective o: objectives)
			{
				if (o instanceof Hunt)
				{
					writer.write("objective "+((Hunt)o).left+"\n");
				}
				else
				{
					writer.write("objective "+(o.finished?"1\n":"0\n"));
				}
			}
			writer.write("end\n");
			
		} catch (IOException e) 
		{
			e.printStackTrace();
		}
	}
}
